fix(codegen)!: wasm for-in/while loop bodies never executed (Closes #255)#257
Merged
Conversation
) Root cause: a layout mismatch in `StmtFor` lowering. `ExprArray` and `ExprIndex` use the canonical array layout `[length@+0][elem0@+4] [elem1@+8]…` (base pointer at the length word). `StmtFor` (all four branches: PatVar, PatWildcard, PatTuple, fallback) instead read the length from `arr_ptr - 4` and element `i` from `arr_ptr + i*4`. So: - length was loaded from 4 bytes *before* the array (garbage, typically 0) ⇒ `index >= length` immediately ⇒ **loop body ran zero times**; - element addressing was off by one word (would read the length as elem 0); for tuple lists this also trapped `memory access out of bounds`. `while` was correct, but any `while` bounded by a `for`-derived length (the idiomatic `vlen` helper) inherited the zero-length and never ran. Fix: all StmtFor branches now read length from `arr_ptr + 0` and element `i` via `I32Load (2, 4)` over `arr_ptr + i*4` (= `arr + 4 + i*4`), matching ExprArray/ExprIndex exactly. Tuple sub-field extraction was already correct (tuples have no length prefix) and is unchanged. Pre-existing since at least `81a59bf`; never caught because `tests/codegen/test_for_loop.affine` had **no** `.mjs`, so the harness compiled but never executed it. Added asserting harnesses: - `test_for_loop.mjs` (for-in over [Int] ⇒ 15) - `while_loop.affine` + `test_while_loop.mjs` (while + for-count len + index ⇒ 34) - `for_tuple.affine` + `test_for_tuple.mjs` (for over tuple list + tuple destructure ⇒ 21) Gates: `dune test --force` 271/271; `tools/run_codegen_wasm_tests.sh` all pass (incl. the three new). Zero regression. Unblocks INT-08 (#183) runtime and INT-07 (#182) TEA run loop. Closes #255. Refs #183 #182.
🔍 Hypatia Security ScanFindings: 44 issues detected
View findings[
{
"reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
"type": "banned",
"file": "AI.a2ml",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Superseded by 0-AI-MANIFEST.a2ml",
"type": "banned",
"file": "AI.djot",
"action": "delete",
"rule_module": "root_hygiene",
"severity": "high"
},
{
"reason": "Issue in quality.yml",
"type": "missing_workflow",
"file": "quality.yml",
"action": "create",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in security-policy.yml",
"type": "missing_workflow",
"file": "security-policy.yml",
"action": "create",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
"type": "unpinned_action",
"file": "governance.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/compile.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/lib/runner.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
May 28, 2026
) ## Summary PR #257 (`fix(codegen)!: wasm for-in/while loop bodies never executed (Closes #255)`) shipped 2026-05-19 and resolved the codegen bug that stdlib roadmap rows #3 (`for x in xs` codegen) and #4 (`while` + `mut` codegen) were waiting on. The status markers were stale at `◯ blocked-by-#255`. Flips both rows to `●` with a reference to the closing PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath
added a commit
that referenced
this pull request
Jul 7, 2026
…xed (#678) ## What Issue **#255** (`for-in`/`while` loop bodies never execute in compiled wasm) was fixed in **PR #257** (commit `85488d74`, an ~8-line array-offset correction) and **closed 2026-05-19**. Verified on current `main`: freshly compiled loops execute their bodies (`while`→55, `for`→15, the exact #255 combo fixture→34). But several roadmap docs still advertised #255 as a **live runtime blocker**, falsely gating **INT-08** (DOM reconciler #183), **INT-11** (browser parity), and the DOM bindings row on a resolved bug. ## Fix — 7 references reconciled (docs-only) - `docs/ECOSYSTEM.adoc` — satellite registry + INT-08/INT-11 rows - `docs/bindings-roadmap.adoc` — DOM row #7 - `docs/TECH-DEBT.adoc` — INT-08 row Honest reconciliation: states #255 is fixed (PR #257, closed 2026-05-19) and marks the DOM/INT-08 **end-to-end runtime as "to be re-verified"** rather than asserting it runs (not driven end-to-end in this change). ## Why it matters A false "blocked" on the roadmap misdirects effort — it implies INT-08/INT-11/DOM need a codegen fix that already shipped. Removing it clarifies that the next step there is *runtime re-verification*, not a bug fix. ## Verification (real toolchain — dune 3.24.0) ``` BUILD_EXIT=0 OK: doc-truthing intact — presence invariants + over-claim ratchet (DOC-04/05/08/09). OK: soundness ledger — all 5 properties hold. ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath
added a commit
that referenced
this pull request
Jul 7, 2026
…ess (Refs #183) The dom.affine header said "RUNTIME blocked by #255; no runtime e2e harness is shipped until then (a harness that cannot pass would be dishonest)". #255 was fixed in PR #257 — and the harness now CAN pass, so per the header's own bar, it ships: - affinescript-dom/e2e/run.sh — concatenates src/dom.affine + a driver `main`, compiles to core-WASM, runs under Node against an Int-handle host DOM, asserts the mutation log + final tree. Skips loudly (exit 0) when the compiler or node is absent; AFFINESCRIPT_BIN overrides the binary path for CI. - affinescript-dom/e2e/driver_main.affine — mounts <div id=app class=old>["hello", <span>["x"]]</div>, then reconciles to <div id=app title=t2>["world"]</div>. Exercises every loop-bearing path (the #255 class): render attr+children loops, patch_attrs add+remove (attr_has), and the `while` child-reconcile loop (in-place text patch + surplus-child removal). The affine discipline is load-bearing in the driver itself: `mount` consumes its tree, so reconcile takes a fresh copy — reuse would be a use-after-move type error. - affinescript-dom/e2e/dom_host.mjs — the host: Int-handle node map, mutation log, hard assertions (title added, class removed, text patched in place, span removed, reconcile returned the in-place handle). Observed run (2026-07-07, node 26, dune 3.24 build): query(#root) … setAttr(#2,title=t2) removeAttr(#2,class) setText(#3,"world") remove(#2,#4) <#root> └ <div id="app" title="t2"> └ "world" ALL ASSERTIONS PASS Docs reconciled to VERIFIED (dom.affine header; ECOSYSTEM satellite + INT-08/INT-11 rows; TECH-DEBT INT-08; bindings-roadmap DOM row). INT-11's remaining leg is browser-host parity (this run is Node). Gates: dune build exit 0; doc-truthing OK; soundness ledger all-5 OK; e2e harness exit 0 from the branch's own build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath
added a commit
that referenced
this pull request
Jul 7, 2026
…ess (#680) ## What `dom.affine`'s header said *"RUNTIME blocked by #255; no runtime e2e harness is shipped until then (a harness that cannot pass would be dishonest)."* #255 was fixed in PR #257 — and the harness now **can** pass, so per the header's own bar, it ships. Refs #183 (INT-08). ## The proof `affinescript-dom/e2e/run.sh` concatenates `src/dom.affine` + a driver `main`, compiles to core-WASM, runs under Node against an Int-handle host DOM, and asserts the mutation log + final tree: - mounts `<div id=app class=old>["hello", <span>["x"]]</div>` - reconciles to `<div id=app title=t2>["world"]</div>` - exercises **every loop-bearing path** (the #255 class): render attr+children loops, `patch_attrs` add+remove (via `attr_has`), and the `while` child-reconcile loop — in-place text patch *and* surplus-child removal Observed (node 26, dune 3.24 build): ``` setAttr(#2,title=t2) removeAttr(#2,class) setText(#3,"world") remove(#2,#4) <#root> └ <div id="app" title="t2"> └ "world" ALL ASSERTIONS PASS — reconciler ran end-to-end (loops executed) ``` A nice detail: the affine discipline is load-bearing in the driver itself — `mount` **consumes** its tree, so the reconcile call takes a freshly built copy; reuse would be a use-after-move type error. ## Docs reconciled to VERIFIED `dom.affine` header; `ECOSYSTEM.adoc` (satellite + INT-08/INT-11 rows); `TECH-DEBT.adoc` (INT-08); `bindings-roadmap.adoc` (DOM row). INT-11's remaining leg is **browser-host** parity (this run is Node). ## Gates `dune build` exit 0 · doc-truthing OK · soundness ledger all-5 OK · e2e harness exit 0 from this branch's own build. Harness skips loudly (exit 0) without the compiler or node; `AFFINESCRIPT_BIN` overrides the binary path for CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix #255 — wasm
for-in/whileloop bodies never executedRoot cause
Layout mismatch in
StmtForlowering (lib/codegen.ml).ExprArray/ExprIndexuse the canonical array layout[length@+0][elem0@+4]…(base pointer at the length word). All four
StmtForbranches insteadread length from
arr_ptr − 4and elementifromarr_ptr + i*4:index ≥ lengthimmediately → loop body ran zero times;memory access out of bounds.whileitself was correct, but the idiomaticwhile i < vlen(xs)inherited the zero length (vlen usesfor) and never ran.Fix
All StmtFor branches read length from
arr_ptr + 0and elementiviaI32Load (2, 4)overarr_ptr + i*4, matchingExprArray/ExprIndexexactly. Tuple sub-field extraction unchanged (correct).Why it was never caught
tests/codegen/test_for_loop.affineexisted but had no.mjs, so the harness compiled it and never ran it. Pre-existing since ≥81a59bf(not a regression). Added asserting harnesses:test_for_loop.mjs(⇒15),while_loop.affine+test_while_loop.mjs(⇒34),for_tuple.affine+test_for_tuple.mjs(⇒21).Verification
dune test --force271/271;tools/run_codegen_wasm_tests.shall pass incl. the three new. Zero regression.Unblocks INT-08 (#183) runtime and INT-07 (#182) TEA run loop.
Closes #255. Refs #183 #182.
🤖 Generated with Claude Code